package com.hqd.ch03.v51.boot.context.config;

import com.hqd.ch03.v51.core.env.PropertySource;

import java.util.*;
import java.util.function.Consumer;

/**
 * 配置数据
 */
public final class ConfigData {

    public static final ConfigData EMPTY = new ConfigData(Collections.emptySet());
    /**
     * 常规配置
     */
    private final List<PropertySource<?>> propertySources;
    /**
     * 可选配置
     */
    private final PropertySourceOptions propertySourceOptions;

    public ConfigData(Collection<? extends PropertySource<?>> propertySources, Option... options) {
        this(propertySources, PropertySourceOptions.always(Options.of(options)));
    }

    public ConfigData(Collection<? extends PropertySource<?>> propertySources,
                      PropertySourceOptions propertySourceOptions) {
        this.propertySources = Collections.unmodifiableList(new ArrayList<>(propertySources));
        this.propertySourceOptions = propertySourceOptions;
    }

    public List<PropertySource<?>> getPropertySources() {
        return this.propertySources;
    }

    @Deprecated
    public Set<Option> getOptions() {
        return this.propertySourceOptions.get(null).asSet();
    }

    public Options getOptions(PropertySource<?> propertySource) {
        Options options = this.propertySourceOptions.get(propertySource);
        return (options != null) ? options : Options.NONE;
    }

    public enum Option {

        /**
         * 忽略源文件中的所有导入属性
         */
        IGNORE_IMPORTS,

        /**
         * 忽略所有配置文件激活并包含属性
         *
         * @since 2.4.3
         */
        IGNORE_PROFILES,

        /**
         * 表示源是“特定于配置文件的”，应该包含在后面
         * 特定于配置文件的兄弟导入。
         */
        PROFILE_SPECIFIC;

    }

    @FunctionalInterface
    public interface PropertySourceOptions {

        PropertySourceOptions ALWAYS_NONE = new AlwaysPropertySourceOptions(Options.NONE);

        static PropertySourceOptions always(Option... options) {
            return always(Options.of(options));
        }

        static PropertySourceOptions always(Options options) {
            if (options == Options.NONE) {
                return ALWAYS_NONE;
            }
            return new AlwaysPropertySourceOptions(options);
        }

        Options get(PropertySource<?> propertySource);

    }

    private static class AlwaysPropertySourceOptions implements PropertySourceOptions {

        private final Options options;

        AlwaysPropertySourceOptions(Options options) {
            this.options = options;
        }

        @Override
        public Options get(PropertySource<?> propertySource) {
            return this.options;
        }

    }

    /**
     * 可选配置
     */
    public static final class Options {
        public static final Options NONE = new Options(Collections.emptySet());

        private final Set<Option> options;

        private Options(Set<Option> options) {
            this.options = Collections.unmodifiableSet(options);
        }

        public static Options of(Option... options) {
            if (options.length == 0) {
                return NONE;
            }
            return new Options(EnumSet.copyOf(Arrays.asList(options)));
        }

        Set<Option> asSet() {
            return this.options;
        }

        public boolean contains(Option option) {
            return this.options.contains(option);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            Options other = (Options) obj;
            return this.options.equals(other.options);
        }

        @Override
        public int hashCode() {
            return this.options.hashCode();
        }

        @Override
        public String toString() {
            return this.options.toString();
        }

        public Options without(Option option) {
            return copy((options) -> options.remove(option));
        }

        public Options with(Option option) {
            return copy((options) -> options.add(option));
        }

        private Options copy(Consumer<EnumSet<Option>> processor) {
            EnumSet<Option> options = EnumSet.noneOf(Option.class);
            options.addAll(this.options);
            processor.accept(options);
            return new Options(options);
        }

    }

}
